﻿using System;
using System.IO;
using System.Threading;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.Collections.Generic;

using SBPweb.PROvibe.Shared.Communication;
using SBPweb.PROvibe.Shared.Serialization;

namespace SBPweb.PROvibe.Client.Framework.Communication
{

    public class ClientChannel
    {

        #region Private fields

        private SynchronizationContext threadUI = null;
        private IChannelFactory<IDuplexSessionChannel> channelFactory;
        private Dictionary<Type, MulticastDelegate> dictMessageHandlers = new Dictionary<Type, MulticastDelegate>(); 

        #endregion

        #region Constructor logic

        public ClientChannel(string url, string action, string actionData, string sessionId)
        {
            ServiceUrl = url;
            Action = action;
            ActionData = actionData;
            SessionId = sessionId;
            threadUI = SynchronizationContext.Current;
        }

        #endregion

        #region Property accessors

        public string ServiceUrl { get; set; }
        public string Action { get; set; }
        public string ActionData { get; set; }
        public string SessionId { get; set; }

        #endregion

        #region Events

        #endregion

        #region Methods

        public void Start()
        {
            // Instantiate the binding and set the time-outs
            PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding()
            {
                ReceiveTimeout = TimeSpan.FromMinutes(1),
                InactivityTimeout = TimeSpan.FromMinutes(2)
            };

            // Instantiate and open channel factory from binding
            channelFactory = binding.BuildChannelFactory<IDuplexSessionChannel>(new BindingParameterCollection());

            IAsyncResult factoryOpenResult = channelFactory.BeginOpen(new AsyncCallback(OnOpenCompleteFactory), channelFactory);
            if (factoryOpenResult.CompletedSynchronously)
            {
                CompleteOpenFactory(factoryOpenResult);
            }
        }

        public void Stop()
        {
            IAsyncResult factoryCloseResult = this.channelFactory.BeginClose(new AsyncCallback(OnCloseCompleteFactory), channelFactory);
            if (factoryCloseResult.CompletedSynchronously)
            {
                CompleteCloseFactory(factoryCloseResult);
            }
            dictMessageHandlers.Clear();
        }

        #region Handler handling :)

        public void AddHandler(Type type, EventHandler<MessageContentEventArgs> handler)
        {
            if (dictMessageHandlers.ContainsKey(type))
            {
                MulticastDelegate md = dictMessageHandlers[type];
                dictMessageHandlers[type] = (MulticastDelegate)MulticastDelegate.Combine(md, (MulticastDelegate)handler);
            }
            else
            {
                dictMessageHandlers.Add(type, (MulticastDelegate)handler);
            }
        }

        public void RemoveHandler(Type type, EventHandler<MessageContentEventArgs> handler)
        {
            if (dictMessageHandlers.ContainsKey(type))
            {
                MulticastDelegate md = dictMessageHandlers[type];
                dictMessageHandlers[type] = (MulticastDelegate)MulticastDelegate.Remove(md, (MulticastDelegate)handler);
            }
            else
            {
                dictMessageHandlers.Remove(type);
            }
        }

        #endregion

        #region Message handling

        private void ReceiveLoop(IDuplexSessionChannel channel)
        {
            // Start listening for callbacks.
            IAsyncResult result = channel.BeginReceive(new AsyncCallback(OnReceiveComplete), channel);
            if (result.CompletedSynchronously) CompleteReceive(result);
        }

        private void OnMessageReceived(object msg)
        {
            Message message = msg as Message;
            DataContractSerializer serializer = new DataContractSerializer(typeof(MessageBody), MessageContentBase.DataContactTypes);
            MessageBody mb = message.GetBody<MessageBody>(serializer);
            Type type = mb.Content.GetType();
            if(!dictMessageHandlers.ContainsKey(type))
            {
                return;
            }
            Delegate del = dictMessageHandlers[type];
            del.DynamicInvoke(this, new MessageContentEventArgs(mb.Content));
        }

        private void OnOpenCompleteFactory(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteOpenFactory(result);
        }

        private void OnOpenCompleteChannel(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteOpenChannel(result);
        }

        private void OnCloseCompleteFactory(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteCloseFactory(result);
        }

        private void OnSend(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteOnSend(result);
        }

        private void OnReceiveComplete(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteReceive(result);
        }

        private void OnCloseChannel(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
                return;
            else
                CompleteCloseChannel(result);
        }

        private void CompleteOpenFactory(IAsyncResult result)
        {
            IChannelFactory<IDuplexSessionChannel> factory =
                (IChannelFactory<IDuplexSessionChannel>)result.AsyncState;

            factory.EndOpen(result);

            // The factory is now open. Create and open a channel from the channel factory.
            IDuplexSessionChannel channel = factory.CreateChannel(new EndpointAddress(ServiceUrl));

            IAsyncResult channelOpenResult = channel.BeginOpen(new AsyncCallback(OnOpenCompleteChannel), channel);
            if (channelOpenResult.CompletedSynchronously)
            {
                CompleteOpenChannel(channelOpenResult);
            }
        }

        private void CompleteOpenChannel(IAsyncResult result)
        {
            IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;

            channel.EndOpen(result);

            // Channel is now open. Send message
            Message message = Message.CreateMessage(channel.GetProperty<MessageVersion>(), Action, ActionData);
            IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
            if (resultChannel.CompletedSynchronously)
            {
                CompleteOnSend(resultChannel);
            }

            //Start listening for callbacks from the service
            ReceiveLoop(channel);
        }

        private void CompleteOnSend(IAsyncResult result)
        {
            IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
            channel.EndSend(result);
        }

        private void CompleteReceive(IAsyncResult result)
        {
            //A callback was received so process data
            IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;

            try
            {
                Message receivedMessage = channel.EndReceive(result);

                // Show the service response in the UI.
                if (receivedMessage != null)
                {
//                    string text = receivedMessage.GetBody<string>();
                    threadUI.Post(OnMessageReceived, receivedMessage);
                }

                ReceiveLoop(channel);
            }
            catch (CommunicationObjectFaultedException exp)
            {
                threadUI.Post(delegate(object msg) { System.Windows.Browser.HtmlPage.Window.Alert(msg.ToString()); }, exp.Message);
            }
        }

        private void CompleteCloseFactory(IAsyncResult result)
        {
            IChannelFactory<IDuplexSessionChannel> factory =
                (IChannelFactory<IDuplexSessionChannel>)result.AsyncState;

            factory.EndClose(result);
        }

        private void CompleteCloseChannel(IAsyncResult result)
        {
            IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
            channel.EndClose(result);
        }

        #endregion

        #endregion

    }

}